package com.jiyongjun.component.core.impl;

import com.jiyongjun.component.annotation.ApiMethodMetadata;
import com.jiyongjun.component.config.LogSwitch;
import com.jiyongjun.component.core.model.DubboLogContext;
import com.jiyongjun.component.enums.LogConstant;
import com.jiyongjun.component.enums.LogResultTypeEnum;
import com.jiyongjun.component.exception.convertor.DubboConsumerExceptionConvertor;
import com.jiyongjun.component.exception.parser.ExceptionParseResult;
import com.jiyongjun.component.utils.JsonUtils;
import com.jiyongjun.component.utils.LogContextUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;

import java.lang.reflect.Method;
import java.util.Comparator;
import java.util.HashMap;

import static com.jiyongjun.component.enums.LogConstant.LOG_EXCEPTION_KEYWORD;

/**
 * <h2></h2>
 * <h3>描述：处理流程</h3>
 * <ol>
 *     <li>{@link DubboWatchDogClientTemplate#onDubboEnter}</li>
 *     <li>{@link DubboWatchDogClientTemplate#onDubboResponse}</li>
 *     <ol>
 *        <li>{@link DubboWatchDogClientTemplate#onParseException}</li>
 *        <li>{@link DubboWatchDogClientTemplate#onLogException}</li>
 *        <li>{@link DubboWatchDogClientTemplate#onHandleResponseWithoutException}</li>
 *     </ol>
 *
 * </ol>
 *
 * @author yongjun.jyj
 * @since 2022/7/14 20:27
 */
@Slf4j
public class DubboWatchDogClientTemplate {
    @Autowired
    private DubboConsumerExceptionConvertor dubboConsumerExceptionConvertor;
    @Autowired
    private DubboWatchDogServerTemplate watchDogServerTemplate;

    public DubboLogContext onDubboEnter(Invocation invocation) {
        String apiUniqueName = LogContextUtils.generateMethodUniqueName(getMethod(invocation));
        DubboLogContext existLogContext = DubboLogContext.getLogContext(apiUniqueName, false);
        if (existLogContext != null) {
            log.error("{}|日志上下文未自动关闭|apiUniqueName={}", LOG_EXCEPTION_KEYWORD, apiUniqueName);
            existLogContext.drop();
        }
        DubboLogContext logContext = DubboLogContext.getLogContext(apiUniqueName, true)
                .setMethod(getMethod(invocation))
                .setApiName(LogContextUtils.generateMethodSimpleName(getMethod(invocation)))
                .setArgs(invocation.getArguments());

        DubboClientTimeoutLogContextMap.put(invocation, logContext);

        return logContext;
    }

    private static Method getMethod(Invocation invocation) {
        return invocation.getServiceModel().getServiceModel().getMethod(invocation.getMethodName(), invocation.getParameterTypes()).getMethod();
    }

    private ApiMethodMetadata getApiMethodMetadata(Invocation invocation) {
        return LogContextUtils.getApiMethodMetadata(invocation);
    }


    public void onDubboResponse(Invocation invocation, Result rpcResult) {
        DubboLogContext dubboLogContext = DubboClientTimeoutLogContextMap.remove(invocation);
        String apiName = LogContextUtils.generateMethodUniqueName(getMethod(invocation));

        DubboLogContext logContext = DubboLogContext.getLogContext(apiName, false);
        if (logContext == null) {
            if (dubboLogContext != null) {
                log.info("{}|dubbo调用超时|apiUniqueName={}", LogConstant.LOG_INFO_KEYWORD, apiName);
                logContext = dubboLogContext;
            } else {
                log.error("{}|根据apiName未获取到日志上下文|apiName={}", LOG_EXCEPTION_KEYWORD, apiName);
                logContext = DubboLogContext.getLogContext(apiName, true);
            }
        }

        try {
            // 解析异常
            ExceptionParseResult exceptionParseResult = onParseException(invocation, rpcResult);
            if (exceptionParseResult != null) {
                // 记录异常
                onLogException(exceptionParseResult, logContext);
                onHandleException(rpcResult, exceptionParseResult);
            } else {
                // 记录结果
                onHandleResponseWithoutException(rpcResult, logContext);
            }
        } catch (Exception e) {
            log.error("{}|记录dubbo日志异常|msg={}", LOG_EXCEPTION_KEYWORD, e.getMessage(), e);
        } finally {
            logContext.close();
        }

    }

    private ExceptionParseResult onParseException(Invocation invocation, Result rpcResult) {
        if (rpcResult.getException() != null) {
            ExceptionParseResult exceptionParseResult = new ExceptionParseResult();
            exceptionParseResult.setRaw(rpcResult.getException());
            exceptionParseResult.setExceptionType(LogResultTypeEnum.SERVICE_EXCEPTION.getCode());
            exceptionParseResult.setMsgCode(rpcResult.getException().getMessage());
            exceptionParseResult.setMsgInfo(rpcResult.getException().getMessage());
        }
        return watchDogServerTemplate.onParseException(invocation, rpcResult);
    }


    private void onLogException(ExceptionParseResult exceptionParseResult, DubboLogContext logContext) {
       watchDogServerTemplate.onLogException(exceptionParseResult, logContext);
    }

    private void onHandleException(Result rpcResult, ExceptionParseResult exceptionParseResult) {
        dubboConsumerExceptionConvertor.handleException(rpcResult, exceptionParseResult);
    }


    private void onHandleResponseWithoutException(Result rpcResult, DubboLogContext logContext) {
        if (!LogSwitch.LOG_RESULT_API_BLACKLIST.contains(logContext.getApiUniqueName())) {
            logContext.setResult(rpcResult.getValue());
        }

        dubboConsumerExceptionConvertor.handleResponse(rpcResult, logContext);
    }

}
